package org.linlinjava.litemall.core.util;

import cn.hutool.core.collection.CollectionUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

public class BigDecimalUtil {

    private static final int DEFAULT_SCALE = 2;
    private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;

    /**
     * 将 null 转为 BigDecimal.ZERO，避免空指针异常。
     * @param value 可能为 null 的 BigDecimal 值
     * @return 非 null 的 BigDecimal 值
     */
    public static BigDecimal nullToZero(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }

    // ========== 基础四则运算 ==========

    /**
     * 两个 BigDecimal 相加，null 值视为 0。
     * @param a 第一个加数，可能为 null
     * @param b 第二个加数，可能为 null
     * @return 和，null 转为 0 后计算
     */
    public static BigDecimal add(BigDecimal a, BigDecimal b) {
        return nullToZero(a).add(nullToZero(b));
    }

    /**
     * 两个 BigDecimal 相减，null 值视为 0。
     * @param a 被减数，可能为 null
     * @param b 减数，可能为 null
     * @return 差，null 转为 0 后计算
     */
    public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
        return nullToZero(a).subtract(nullToZero(b));
    }

    /**
     * 两个 BigDecimal 相乘，null 值视为 0。
     * @param a 第一个乘数，可能为 null
     * @param b 第二个乘数，可能为 null
     * @return 积，null 转为 0 后计算
     */
    public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
        return nullToZero(a).multiply(nullToZero(b));
    }

    /**
     * 两个 BigDecimal 相除，null 或 0 的除数返回 0，结果默认保留两位小数，四舍五入。
     * @param a 被除数，可能为 null
     * @param b 除数，可能为 null 或 0
     * @return 商，null 转为 0 后计算，除数为 0 返回 0
     */
    public static BigDecimal divide(BigDecimal a, BigDecimal b) {
        return divide(a, b, DEFAULT_SCALE);
    }

    /**
     * 两个 BigDecimal 相除，null 或 0 的除数返回 0，结果保留指定小数位数，四舍五入。
     * @param a 被除数，可能为 null
     * @param b 除数，可能为 null 或 0
     * @param scale 保留小数位数
     * @return 商，null 转为 0 后计算，除数为 0 返回 0
     */
    public static BigDecimal divide(BigDecimal a, BigDecimal b, int scale) {
        if (b == null || b.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return nullToZero(a).divide(b, scale, DEFAULT_ROUNDING_MODE);
    }

    // ========== 列表求和 ==========

    /**
     * 对列表中的元素提取 BigDecimal 字段后求和，可传入过滤器筛选元素。
     * 适用于需要对复杂对象集合根据某字段求和的场景。
     * @param list 元素集合，允许 null 或空集合
     * @param mapper 提取 BigDecimal 字段的函数
     * @param filter 过滤条件，允许为 null（表示不过滤）
     * @param <T> 集合元素类型
     * @return 满足条件元素对应字段的和，null 或空集合返回 0
     */
    public static <T> BigDecimal sum(Collection<T> list,
                                     Function<T, BigDecimal> mapper,
                                     Predicate<T> filter) {
        if (CollectionUtil.isEmpty(list)) {
            return BigDecimal.ZERO;
        }
        return list.stream()
                .filter(Objects::nonNull)
                .filter(filter != null ? filter : t -> true) // 默认不过滤
                .map(mapper)
                .filter(Objects::nonNull)
                .map(BigDecimalUtil::nullToZero)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 对列表中的元素提取 BigDecimal 字段后求和，不带过滤器。
     * 适用于简单根据字段求和的场景。
     * @param list 元素集合，允许 null 或空集合
     * @param mapper 提取 BigDecimal 字段的函数
     * @param <T> 集合元素类型
     * @return 元素对应字段的和，null 或空集合返回 0
     */
    public static <T> BigDecimal sum(Collection<T> list,
                                     Function<T, BigDecimal> mapper) {
        return sum(list, mapper, null);
    }

    /**
     * 对 BigDecimal 类型的列表求和，null 或空集合返回 0。
     * 适用于直接对 BigDecimal 列表求和的场景。
     * @param list BigDecimal 集合，允许 null 或空集合
     * @return 列表元素之和
     */
    public static BigDecimal sum(Collection<BigDecimal> list) {
        if (CollectionUtil.isEmpty(list)) {
            return BigDecimal.ZERO;
        }
        return list.stream()
                .filter(Objects::nonNull)
                .map(BigDecimalUtil::nullToZero)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    // ========== 比较 ==========

    /**
     * 比较两个 BigDecimal 是否相等，null 视为 0，避免空指针异常。
     * @param a 第一个 BigDecimal，可能为 null
     * @param b 第二个 BigDecimal，可能为 null
     * @return 两者数值是否相等
     */
    public static boolean equals(BigDecimal a, BigDecimal b) {
        return nullToZero(a).compareTo(nullToZero(b)) == 0;
    }

    /**
     * 判断第一个 BigDecimal 是否大于第二个，null 视为 0。
     * @param a 第一个值，可能为 null
     * @param b 第二个值，可能为 null
     * @return a 是否大于 b
     */
    public static boolean greaterThan(BigDecimal a, BigDecimal b) {
        return nullToZero(a).compareTo(nullToZero(b)) > 0;
    }

    /**
     * 判断第一个 BigDecimal 是否小于第二个，null 视为 0。
     * @param a 第一个值，可能为 null
     * @param b 第二个值，可能为 null
     * @return a 是否小于 b
     */
    public static boolean lessThan(BigDecimal a, BigDecimal b) {
        return nullToZero(a).compareTo(nullToZero(b)) < 0;
    }

    /**
     * 判断第一个 BigDecimal 是否大于等于第二个，null 视为 0。
     * @param a 第一个值，可能为 null
     * @param b 第二个值，可能为 null
     * @return a 是否大于等于 b
     */
    public static boolean greaterOrEqual(BigDecimal a, BigDecimal b) {
        return nullToZero(a).compareTo(nullToZero(b)) >= 0;
    }

    /**
     * 判断第一个 BigDecimal 是否小于等于第二个，null 视为 0。
     * @param a 第一个值，可能为 null
     * @param b 第二个值，可能为 null
     * @return a 是否小于等于 b
     */
    public static boolean lessOrEqual(BigDecimal a, BigDecimal b) {
        return nullToZero(a).compareTo(nullToZero(b)) <= 0;
    }

    // ========== 四舍五入 ==========

    /**
     * 对 BigDecimal 进行四舍五入，默认保留两位小数。
     * @param value 需要四舍五入的值，可能为 null
     * @return 四舍五入后的结果，null 转为 0 后处理
     */
    public static BigDecimal round(BigDecimal value) {
        return round(value, DEFAULT_SCALE);
    }

    /**
     * 对 BigDecimal 进行四舍五入，保留指定小数位数。
     * @param value 需要四舍五入的值，可能为 null
     * @param scale 保留小数位数
     * @return 四舍五入后的结果，null 转为 0 后处理
     */
    public static BigDecimal round(BigDecimal value, int scale) {
        return nullToZero(value).setScale(scale, DEFAULT_ROUNDING_MODE);
    }

}
